/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.geoviewer.edit;

import java.awt.Color;
import java.awt.Point;
import java.awt.event.KeyEvent;
import javax.swing.BorderFactory;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.ConnectProvider;
import org.netbeans.api.visual.action.ConnectorState;
import org.netbeans.api.visual.action.ReconnectProvider;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.anchor.AnchorFactory;
import org.netbeans.api.visual.anchor.PointShape;
import org.netbeans.api.visual.graph.GraphScene;
import org.netbeans.api.visual.widget.ConnectionWidget;
import org.netbeans.api.visual.widget.LabelWidget;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Scene;
import org.netbeans.api.visual.widget.Widget;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;

/**
 *
 * @author Timon Veenstra
 */
@Messages({"geo_editor.hint_ctrl_move=Press ctrl to move a point", "geo_editor.hint_shift_connect=Press shift to connect a point to another"})
public class GeoEditorScene extends GraphScene<GeoNode, String> {

    
    //
    //tools
    private static final String CONNECT_TOOL = "connectTool";
    private static final String MOVE_TOOL = "moveTool";
    //
    // layers
    private LayerWidget mainLayer = new LayerWidget(this);
    private LayerWidget connectionLayer = new LayerWidget(this);
    private LayerWidget interractionLayer = new LayerWidget(this);
    private LayerWidget hintsLayer = new LayerWidget(this);
    //
    // actions
    private WidgetAction connectAction = ActionFactory.createConnectAction(interractionLayer, new SceneConnectProvider());
    private WidgetAction reconnectAction = ActionFactory.createReconnectAction(new SceneReconnectProvider());
    //
    //counters
    private long nodeCounter = 0;
    private long edgeCounter = 0;

    public GeoEditorScene() {
        addChild(mainLayer);
        addChild(connectionLayer);
        addChild(interractionLayer);
        addChild(hintsLayer);
        getPriorActions().addAction(new CtrlKeySwitchToolAction());
        getPriorActions().addAction(new ShiftKeySwitchToolAction());

        createLabel(hintsLayer,NbBundle.getMessage(this.getClass(), "geo_editor.hint_ctrl_move"),10,10);
        createLabel(hintsLayer,NbBundle.getMessage(this.getClass(), "geo_editor.hint_shift_connect"),10,30);
    }
    
    /**
     * convenience method to create labels
     * 
     * @param layer
     * @param text
     * @param x
     * @param y 
     */
    private static void createLabel (LayerWidget layer, String text, int x, int y) {
        LabelWidget label = new LabelWidget (layer.getScene (), text);
        label.setPreferredLocation (new Point (x, y));
        layer.addChild (label);
    }    

    public void connectNodes(GeoNode source, GeoNode target) {
        String edge = "" + edgeCounter++;
        addEdge(edge);
        setEdgeSource(edge, source);
        setEdgeTarget(edge, target);
    }

    @Override
    protected Widget attachNodeWidget(GeoNode node) {
        Widget geoNodeWidget = new GeoNodeWidget(this, node);

        geoNodeWidget.createActions(MOVE_TOOL).addAction(ActionFactory.createMoveAction());
//        label.getActions ().addAction (createSelectAction ());
        geoNodeWidget.getActions().addAction(createObjectHoverAction());
        geoNodeWidget.createActions(CONNECT_TOOL).addAction(connectAction);
        mainLayer.addChild(geoNodeWidget);
        return geoNodeWidget;
    }

    @Override
    protected Widget attachEdgeWidget(String edge) {
        ConnectionWidget connection = new ConnectionWidget(this);
//        connection.setTargetAnchorShape (AnchorShape.TRIANGLE_FILLED);
        connection.setEndPointShape(PointShape.SQUARE_FILLED_BIG);
        connection.getActions().addAction(createObjectHoverAction());
        connection.getActions().addAction(createSelectAction());
        connection.getActions().addAction(reconnectAction);
        connectionLayer.addChild(connection);
        return connection;
    }

    @Override
    protected void attachEdgeSourceAnchor(String edge, GeoNode oldSourceNode, GeoNode sourceNode) {
        Widget w = sourceNode != null ? findWidget(sourceNode) : null;
        ((ConnectionWidget) findWidget(edge)).setSourceAnchor(AnchorFactory.createRectangularAnchor(w));
    }

    @Override
    protected void attachEdgeTargetAnchor(String edge, GeoNode oldTargetNode, GeoNode targetNode) {
        Widget w = targetNode != null ? findWidget(targetNode) : null;
        ((ConnectionWidget) findWidget(edge)).setTargetAnchor(AnchorFactory.createRectangularAnchor(w));
    }

    private class SceneConnectProvider implements ConnectProvider {

        private GeoNode source = null;
        private GeoNode target = null;

        @Override
        public boolean isSourceWidget(Widget sourceWidget) {
            Object object = findObject(sourceWidget);
            source = isNode(object) ? (GeoNode) object : null;
            return source != null;
        }

        @Override
        public ConnectorState isTargetWidget(Widget sourceWidget, Widget targetWidget) {
            Object object = findObject(targetWidget);
            target = isNode(object) ? (GeoNode) object : null;
            if (target != null) {
                return !source.equals(target) ? ConnectorState.ACCEPT : ConnectorState.REJECT_AND_STOP;
            }
            return object != null ? ConnectorState.REJECT_AND_STOP : ConnectorState.REJECT;
        }

        @Override
        public boolean hasCustomTargetWidgetResolver(Scene scene) {
            return false;
        }

        @Override
        public Widget resolveTargetWidget(Scene scene, Point sceneLocation) {
            return null;
        }

        @Override
        public void createConnection(Widget sourceWidget, Widget targetWidget) {
            String edge = "edge" + edgeCounter++;
            addEdge(edge);
            setEdgeSource(edge, source);
            setEdgeTarget(edge, target);
        }
    }

    private class SceneReconnectProvider implements ReconnectProvider {

        String edge;
        GeoNode originalNode;
        GeoNode replacementNode;

        @Override
        public void reconnectingStarted(ConnectionWidget connectionWidget, boolean reconnectingSource) {
        }

        @Override
        public void reconnectingFinished(ConnectionWidget connectionWidget, boolean reconnectingSource) {
        }

        @Override
        public boolean isSourceReconnectable(ConnectionWidget connectionWidget) {
            Object object = findObject(connectionWidget);
            edge = isEdge(object) ? (String) object : null;
            originalNode = edge != null ? getEdgeSource(edge) : null;
            return originalNode != null;
        }

        @Override
        public boolean isTargetReconnectable(ConnectionWidget connectionWidget) {
            Object object = findObject(connectionWidget);
            edge = isEdge(object) ? (String) object : null;
            originalNode = edge != null ? getEdgeTarget(edge) : null;
            return originalNode != null;
        }

        @Override
        public ConnectorState isReplacementWidget(ConnectionWidget connectionWidget, Widget replacementWidget, boolean reconnectingSource) {
            Object object = findObject(replacementWidget);
            replacementNode = isNode(object) ? (GeoNode) object : null;
            if (replacementNode != null) {
                return ConnectorState.ACCEPT;
            }
            return object != null ? ConnectorState.REJECT_AND_STOP : ConnectorState.REJECT;
        }

        @Override
        public boolean hasCustomReplacementWidgetResolver(Scene scene) {
            return false;
        }

        @Override
        public Widget resolveReplacementWidget(Scene scene, Point sceneLocation) {
            return null;
        }

        @Override
        public void reconnect(ConnectionWidget connectionWidget, Widget replacementWidget, boolean reconnectingSource) {
            if (replacementWidget == null) {
                removeEdge(edge);
            } else if (reconnectingSource) {
                setEdgeSource(edge, replacementNode);
            } else {
                setEdgeTarget(edge, replacementNode);
            }
        }
    }

    /**
     * action to switch tools when ctrl is pushed
     */
    private static final class CtrlKeySwitchToolAction extends WidgetAction.Adapter {

        @Override
        public State keyPressed(Widget widget, WidgetKeyEvent event) {
            if (event.getKeyCode() == KeyEvent.VK_CONTROL) {
                widget.getScene().setActiveTool(MOVE_TOOL);
            }
            return State.REJECTED;
        }

        @Override
        public State keyReleased(Widget widget, WidgetKeyEvent event) {
            if (event.getKeyCode() == KeyEvent.VK_CONTROL) {
                widget.getScene().setActiveTool(null);
            }
            return State.REJECTED;
        }
    }

    /**
     * action to switch tools when ctrl is pushed
     */
    private static final class ShiftKeySwitchToolAction extends WidgetAction.Adapter {

        @Override
        public State keyPressed(Widget widget, WidgetKeyEvent event) {
            if (event.getKeyCode() == KeyEvent.VK_SHIFT) {
                widget.getScene().setActiveTool(CONNECT_TOOL);
            }
            return State.REJECTED;
        }

        @Override
        public State keyReleased(Widget widget, WidgetKeyEvent event) {
            if (event.getKeyCode() == KeyEvent.VK_SHIFT) {
                widget.getScene().setActiveTool(null);
            }
            return State.REJECTED;
        }
    }
}
